﻿using System;
using System.Collections.Generic;
using System.IO;

namespace biaofile
{
    public class Biao
    {
        public static BiaoType Resolve(Stream stream)
        {
            var pos = stream.Position;
            var biaotype = _resolve(stream);
            stream.Position = pos;
            return biaotype;
        }
        internal static BiaoType _resolve(Stream stream)
        {
            stream.Position = 0;

            var tag = read_ulong(stream);
            switch (tag) // 8 byte
            {
                case 0xE11AB1A1E011CFD0:
                    // https://learn.microsoft.com/zh-cn/previous-versions/office/gg615407(v=office.14)
                    foreach (var office_entryname in read_office_entrynames(stream))
                    switch(office_entryname.ToLower()){
                        case "workbook": return BiaoType.xls;
                        case "powerpoint document": return BiaoType.ppt;
                        case "worddocument": return BiaoType.doc;
                    }

                    return BiaoType.office;
                case 0x0A1A0A0D474E5089: return BiaoType.png;
                case 0x000214010000004C:
                    // 固定值0x0000004C，对应ASCII字符"L"  + 固定GUID值00021401-0000-0000-C000-000000000046
                    if (read_ulong(stream) == 0x000000C000000000 && read_uint(stream) == 0x46000000) return BiaoType.lnk;
                    break;
            }
            switch (tag & 0xffffffffffffff) // 7 byte
            {
                case 0x2E312D46445025:

                    return BiaoType.pdf;
            }
            switch (tag & 0xffffffffffff) // 6 byte
            {
                case 0x613938464947: // GIF89a
                case 0x613738464947: // GIF87a
                    return BiaoType.gif;
            }
            switch (tag & 0xffffffffff) // 5 byte
            {
                case 0x6C6D783F3C:

                    return BiaoType.xml;
                case 0x3E6C467468:

                    return BiaoType.html;
            }
            switch (tag &0xffffffff)
            {
                case 0x04034B50:
                    stream.Position = stream.Length - 6;
                    var central_pos = read_uint(stream);
                    if (central_pos < stream.Length)
                    foreach (var zipentry_name in loop_zip_entrynames(stream, central_pos))
                    switch (zipentry_name)
                    {
                        case "xl/workbook.xml": return BiaoType.xlsx;
                        case "ppt/presentation.xml": return BiaoType.pptx;
                        case "word/document.xml": return BiaoType.docx;
                    }

                    return BiaoType.zip;
                case 0x424A5342: // BSJB 头
                    stream.Position = 12;
                    var bsjb_namesize = read_uint(stream);
                    if (bsjb_namesize > 5 && bsjb_namesize + 16 < stream.Length && read_chars(stream, 5) == "PDB v") return BiaoType.pdb;
                    break;
                case 0x21726152: return BiaoType.rar;
                case 0x30314341: return BiaoType.dwg;
                case 0x53504238: return BiaoType.psd;
                case 0xe0ffd8ff: return BiaoType.jpg;
                case 0x00010000: return BiaoType.ico;
            }
            switch (tag & 0xffff)
            {
                case 0x5A4D: // PE 文件

                    stream.Position = 60;
                    var pe_pos = read_uint(stream);
                    if (pe_pos + 24 < stream.Length)
                    {
                        stream.Position = pe_pos;
                        var pe_signature = read_uint(stream);
                        if (pe_signature == 0x4550)
                        {
                            stream.Position = pe_pos + 22;
                            var pe_type = read_ushort(stream); // PE Characteristics
                            if (has_flag(pe_type, 0x2000)) return BiaoType.dll;
                            if (has_flag(pe_type, 0x0002))
                            {
                                stream.Position = pe_pos + 40;
                                var pe_AddressOfEntryPoint = read_uint(stream);
                                if (stream.Length < 65535 && pe_AddressOfEntryPoint == 0x00001300) return BiaoType.com; // 程序入口固定
                                return BiaoType.exe;
                            }
                        }
                    }
                    break;
                case 0x4D42: return BiaoType.bmp;
            }

            return BiaoType.unknown;
        }
        internal static bool has_flag(ushort tag, ushort cha)
        {
            return (tag & cha) != 0;
        }
        internal static IEnumerable<string> read_office_entrynames(Stream s, int max_count = 16)
        {
            ushort name_size;
            s.Position = 1088;
            var maxpos = s.Length;
            while(s.Position < maxpos)
            {
                name_size = read_ushort(s);
                if (name_size > 64 || name_size == 0) break;

                s.Position -= 66;
                yield return read_chars_2(s, name_size >> 1);
                if (max_count-- < 0) break;
                s.Position += 192 - name_size;
            } 
        }
        internal static IEnumerable<string> loop_zip_entrynames(Stream s, uint central_pos, int max_count = 16)
        {
            s.Position = central_pos;
            ushort name_size, ext_size, commit_size;

            var maxpos = s.Length - 16;
            while(s.Position + 46 <= maxpos)
            {
                s.Position += 28;
                name_size = read_ushort(s);
                ext_size = read_ushort(s);
                commit_size = read_ushort(s);
                s.Position += 12;

                if (s.Position + name_size > maxpos) break;
                yield return read_chars(s, name_size);
                if (max_count-- < 0) break;
                s.Position += ext_size + commit_size;
            }
        }
        internal static ulong read_ulong(Stream s)
        {
            return read_uint(s) | ((ulong)read_uint(s) << 32);
        }
        internal static string read_chars(Stream s, uint length)
        {
            var text = string.Empty;
            char _char;
            for (var i = 0; i < length; i++) if ((_char = (char)s.ReadByte()) != 0x00) text += _char;
            return text;
        }
        internal static string read_chars_2(Stream s, int length)
        {
            var text = string.Empty;
            char _char;
            for (var i = 0; i < length; i++,s.Position++) if ((_char = (char)s.ReadByte()) != 0x00) text += _char;
            return text;
        }
        internal static uint read_uint(Stream s)
        {
            return read_ushort(s) | ((uint)read_ushort(s) << 16);
        }
        internal static ushort read_ushort(Stream s)
        {
            return (ushort)(s.ReadByte() | (s.ReadByte() << 8));
        }
    }
}
